namespace SolidMvvm {
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Globalization;
	using System.IO;

	using Newtonsoft.Json;

	using SolidMvvmInterfaces;

	public class SettingDictionary : SettingBase, ISettingDictionary {
		readonly Dictionary<string, ISetting> dictionary = new Dictionary<string, ISetting>();

		string pathAndFilename;

		public ISetting this[string key] {
			get {
				return dictionary[key];
			}
			set {
				dictionary[key] = value;
			}
		}

		public void Add(string key, ISetting value) {
			dictionary.Add(key, value);
		}

		public void Clear() {
			dictionary.Clear();
		}

		public bool ContainsKey(string key) {
			return dictionary.ContainsKey(key);
		}

		public bool GetAsBool(string key, bool fallback) {
			return dictionary.ContainsKey(key) ? bool.Parse(((ISettingString)dictionary[key]).Value) : fallback;
		}

		public double GetAsDouble(string key, double fallback) {
			return dictionary.ContainsKey(key) ? double.Parse(((ISettingString)dictionary[key]).Value) : fallback;
		}

		public T GetAsEnum<T>(string key, T fallback) where T : struct {
			if (!dictionary.ContainsKey(key))
				return fallback;

			T value;
			if (Enum.TryParse(((ISettingString)dictionary[key]).Value, out value))
				return value;

			throw new ArgumentException("Could not parse the value for key '" + key + "'.");
		}

		public long GetAsLong(string key, long fallback) {
			return dictionary.ContainsKey(key) ? long.Parse(((ISettingString)dictionary[key]).Value) : fallback;
		}

		public string GetAsString(string key, string fallback) {
			return dictionary.ContainsKey(key) ? ((ISettingString)dictionary[key]).Value : fallback;
		}

		public IEnumerator<KeyValuePair<string, ISetting>> GetEnumerator() {
			return dictionary.GetEnumerator();
		}

		public bool Remove(string key) {
			return dictionary.Remove(key);
		}

		public void Save() {
			using (var streamWriter = new StreamWriter(new FileStream(pathAndFilename, FileMode.Create)))
				streamWriter.Write(ToJSONString());
		}

		public void SetAsBool(string key, bool value) {
			dictionary[key] = (SettingString)value.ToString();
		}

		public void SetAsDouble(string key, double value) {
			dictionary[key] = (SettingString)value.ToString(CultureInfo.InvariantCulture);
		}

		public void SetAsEnum(string key, Enum value) {
			dictionary[key] = (SettingString)value.ToString();
		}

		public void SetAsLong(string key, long value) {
			dictionary[key] = (SettingString)value.ToString(CultureInfo.InvariantCulture);
		}

		public void SetAsString(string key, string value) {
			dictionary[key] = (SettingString)value;
		}

		public override string ToJSONString() {
			using (var stringWriter = new StringWriter()) {
				using (var jsonTextWriter = new JsonTextWriter(stringWriter) { Formatting = Formatting.Indented }) {
					jsonTextWriter.WriteStartObject();
					foreach (var keyValuePair in dictionary) {
						jsonTextWriter.WritePropertyName(keyValuePair.Key);
						jsonTextWriter.WriteRawValue(keyValuePair.Value.ToJSONString());
					}
					jsonTextWriter.WriteEnd();
				}
				return stringWriter.ToString();
			}
		}

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}

		//DispatcherTimer dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };

		//public SettingDictionary()
		//{
		//	dispatcherTimer.Tick += (sender, args) =>  Save();

		//}

		// Starts with StartObject
		public static SettingDictionary FromJsonTextReader(JsonTextReader jsonTextReader) {
			var settingDictionary = new SettingDictionary();

			if (jsonTextReader.TokenType == JsonToken.None)
				return settingDictionary; // Empty to start with.

			if (jsonTextReader.TokenType != JsonToken.StartObject)
				throw new GeneralException("Expected JSON StartObject token.", null);

			while (jsonTextReader.Read() && jsonTextReader.TokenType != JsonToken.EndObject) {
				if (jsonTextReader.TokenType != JsonToken.PropertyName)
					throw new GeneralException("Expected JSON PropertyName token.", null);

				var key = (string)jsonTextReader.Value;
				jsonTextReader.Read();
				switch (jsonTextReader.TokenType) {
					case JsonToken.String:
						settingDictionary.Set(key, SettingString.FromJsonTextReader(jsonTextReader));
						break;
					case JsonToken.StartArray:
						settingDictionary.Set(key, SettingCollection.FromJsonTextReader(jsonTextReader));
						break;
					case JsonToken.StartObject:
						settingDictionary.Set(key, FromJsonTextReader(jsonTextReader));
						break;
					default:
						throw new GeneralException("Expected JSON String, StartArray, or StartObject token.", null);
				}
			}

			return settingDictionary;
		}

		public static SettingDictionary FromFile(string pathAndFilename) {
			if (pathAndFilename == null)
				throw new ArgumentNullException(nameof(pathAndFilename));

			var directoryName = Path.GetDirectoryName(pathAndFilename);
			if (directoryName == null)
				throw new ArgumentNullException(nameof(directoryName));

			if (!Directory.Exists(directoryName))
				Directory.CreateDirectory(directoryName);

			if (!File.Exists(pathAndFilename))
				using (File.Create(pathAndFilename)) { }

			using (var streamReader = new StreamReader(new FileStream(pathAndFilename, FileMode.Create)))
			using (var jsonTextReader = new JsonTextReader(streamReader)) {
				jsonTextReader.Read();

				var settingDictionary = FromJsonTextReader(jsonTextReader);
				settingDictionary.pathAndFilename = pathAndFilename;
				//settingDictionary.dispatcherTimer.Start();
				return settingDictionary;
			}
		}

		public void Set(string key, SettingBase value) {
			dictionary[key] = value;
		}
	}
}
